<template>
  <div class="editor">
    <tiny-editor
      api-key="izuqszdbzd4kyrez7fr6q94e6p97c135qdyoe9h1ems9p1sr"
      :init="init"
    ></tiny-editor>
  </div>
</template>

<script>
  import tinymce from 'tinymce/tinymce';
  import TinyEditor from '@tinymce/tinymce-vue';
  import ThemeMixins from '../mixins/Theme';
  import 'tinymce/themes/silver';
  import 'tinymce/icons/default';

  import 'tinymce/plugins/autoresize';
  import 'tinymce/plugins/wordcount';
  import 'tinymce/plugins/paste';
  import 'tinymce/plugins/image';

  const StylePlaceholder = {
    bold: `<strong>&#8203</strong>`,
    italic: `<em>&#8203</em>`,
    underline: `<span style="text-decoration: underline;">​</span>`,
    strikethrough: `<span style="text-decoration: line-through;">​</span>`,
  };

  export default {
    mixins: [ThemeMixins],
    props: {
      cvIdPath: {
        default: '',
      },
      // 编辑器返回格式
      contentType: {
        type: String,
        default: 'raw',
      },
      placeholder: {
        type: String,
        default: '正文描述，请用图文和大家分享视频创作背后的故事吧~',
      },
      workType: {
        type: String,
        default: '',
      },
      // 如果当前是被当做第三方库的组件使用的话,需要配置css-from-root属性,
      // 从该项目根目录下访问
      cssFromRoot: {
        type: Boolean,
        default: false,
      },
    },
    components: {
      TinyEditor,
    },
    computed: {
      overStyle() {
        return this.contentLength < this.minLength || this.contentLength > this.maxLength;
      },
      overStyleText() {
        if (this.overStyle) {
          if (this.contentLength < this.minLength) {
            return `还不够${this.minLength}字`;
          }
          return '字数已超限';
        }
        return '';
      },
    },
    data() {
      return {
        contentLength: 0,
        editor: null,
        flag: true,
        editorHeight: 0,
        resizeFlag: true,
        placeholderKeyAttr: 'data-mce-placeholder',
        init: {
          setup: (editor) => {
            this.editor = editor;
            this.editor.on('init', () => {
              this.editorHeight = this.editor.container.getBoundingClientRect().height;
              // 在修改的时候,就先看看长度是多长
              this.getContentLength();
            });
            // 纯文字不支持回车键
            this.editor.on('keydown', (evt) => {
              if (this.contentType === 'text' && evt.keyCode === 13) {
                evt.preventDefault();
              }
            });
            // 这个玩意真的找了好久，好久，好久，好久，好久
            // 每次鼠标选中内容或者定位到不同的地方都会触发NodeChange这个事件
            this.editor.on('NodeChange', (evt) => {
              // 然后利用这个formatter来match当前的内容是否匹配了一个样式

              this.$EventBus.$emit(
                'editor:nodechange',
                this.editor.formatter.matchAll([
                  'h1',
                  'bold',
                  'italic',
                  'underline',
                  'strikethrough',
                  'alignjustify',
                  'alignfull',
                  'alignright',
                  'aligncenter',
                  'alignleft',
                ]),
              );
            });
            this.editor.on('focus', (evt) => {
              // Remove Placeholder
              this.removePlaceHolder();
              this.$EventBus.$emit(`editor:${evt.type}`);
            });
            this.editor.on('blur', (evt) => {
              console.log('blur');
              this.setPlaceHolder();
            });
          },

          language_url: '/static/tinymce/langs/zh_CN.js', // 中文语言包路径
          language: 'zh_CN',
          skin_url: '/static/tinymce/skins/ui/oxide',
          content_css: '/static/tinymce/custom.css',
          toolbar:
            'code undo redo restoredraft | forecolor backcolor bold italic underline strikethrough link codesample | alignleft aligncenter alignright alignjustify outdent indent  formatpainter | \
    table image media charmap hr pagebreak insertdatetime | bdmap fullscreen preview',
          relative_urls: false,
          plugins: 'autoresize wordcount paste image',
          forced_root_block: 'p',
          min_height: 400,
          max_height: 700,
          autoresize_bottom_margin: 0,
          // 禁止图片表格多媒体元素缩放
          object_resizing: false,
          // paste
          paste_preprocess: (plugin, args) => {
            // 去掉div标签,去掉id/class
            args.content = args.content
              .replace(/<div|<pre|<section|<a/g, '<p')
              .replace(/<\/div|<\/pre|<\/section|<\/a/g, '</p')
              .replace(/(id|class|style|width|height|href|data.*?)=['"].*?['"]/g, '')
              .replace(/(<p><iframe.*?>.*?<\/iframe><\/p>)/g, '');

            this.pasteImageHandler(args);
          },
          toolbar_mode: 'wrap',
          // external_plugins: { powerpaste: '/static/tinymce/plugins/powerpaste/plugin.min.js' },
          //选择文本时的快捷栏
          quickbars_selection_toolbar:
            'bold italic underline forecolor backcolor | fontselect |fontsizeselect | formatselect | quicklink blockquote',
          //选中图片时的快捷栏
          quickbars_image_toolbar: 'alignleft aligncenter alignright quicklink |  imageoptions',
          // plugins:
          //   'preview  searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap hr nonbreaking insertdatetime lists wordcount imagetools textpattern autosave  autoresize ',

          contextmenu: 'link image imagetools inserttable | cell row column deletetable | headings',
          content_style: 'p {margin: 0;}',
          fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',
          // remove_script_host: false,
          font_formats:
            '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;',
          // branding: false,

          images_upload_handler: (blobInfo, success, failure, progress) => {
            const file = new File([blobInfo.blob()], blobInfo.filename());
            this.$UploadCore({
              isPublic: true,
              file,
              folder: `${['guide', this.cvIdPath].filter((p) => p).join('/')}`,
            })
              .then((res) => {
                success(res.url);
                // 如果是拖动上传的话，由于父组件无法更新上传之后的值，这里重新赋值
                this.$emit('input', this.editor.getContent());
                this.$message.success('图片添加成功!');
              })
              .catch((err) => {
                console.log(err);
                this.$message.error('图片上传失败');
              });
          },
        },
        EventHandlerMap: {
          'editor:cmd': (...res) => {
            const [_] = res;
            const command = _.toLowerCase();
            // 这几种模式就需要先插入一个空的容器,避免影响整行
            if (['bold', 'italic', 'underline', 'strikethrough'].includes(command)) {
              const { collapsed } = this.editor.selection.getRng();
              if (collapsed) {
                const macthedStyle = this.editor.formatter.matchAll([
                  'bold',
                  'italic',
                  'underline',
                  'strikethrough',
                ]);

                if (macthedStyle.includes(command)) {
                  this.execCommand('mceToggleFormat', false, command);
                } else {
                  this.execCommand('mceInsertContent', false, StylePlaceholder[command]);
                }
              } else {
                this.execCommand(...res);
              }
            } else {
              this.execCommand(...res);
            }
            // 修改完成之后, 聚焦到输入框
            if (res[0] != 'mceFocus') {
              this.execCommand('editor:cmd', 'mceFocus', false);
            }
          },
          'etb:color': (color) => {
            const { collapsed } = this.editor.selection.getRng();
            if (collapsed) {
              if (!color) {
                this.execCommand(
                  'mceInsertContent',
                  false,
                  `<span class="color-default">&#8203</span>`,
                );
              } else {
                // 如果没有选区
                // 则注入一个带颜色的空间, &#8203是零宽的空格字符用于占位
                this.execCommand(
                  'mceInsertContent',
                  false,
                  `<span style="color: ${color}">&#8203</span>`,
                );
              }
            } else {
              // 如果是个空值则设置为默认值
              if (!color) {
                // IMPORTANT: 非常重要的点!!!!!!! 这个解决方案花了我3个小时
                // https://www.tiny.cloud/docs/advanced/editor-command-identifiers/
                return this.execCommand('mceRemoveTextcolor', 'forecolor');
              }
              // 如果有选区
              // 设置选区的颜色
              this.execCommand('mceApplyTextcolor', 'forecolor', color);
            }
          },
        },
      };
    },
    watch: {
      $attrs() {
        // 避免重复设置内容导致页面内容刷新
        if (
          this.flag &&
          this.$attrs.value !== this.editor.getContent({ format: this.contentType })
        ) {
          // FIXME: 观点加载草稿会有问题,暂时这样处理
          this.$nextTick(() => {
            this.editor.setContent(this.$attrs.value || '', { format: this.contentType });
            if (this.$attrs.value) {
              this.removePlaceHolder();
            }
            this.getContentLength();
          });
        }
        this.flag = true;
      },
      isDark() {
        if (!this.editor) return;
        if (this.isDark) {
          this.editor.getBody().classList.add('dark');
        } else {
          this.editor.getBody().classList.remove('dark');
        }
      },
    },
    beforeDestroy() {
      this.$EventBus.$off('editor:cmd', this.EventHandlerMap['editor:cmd']);
      // 修改颜色
      this.$EventBus.$off('etb:color', this.EventHandlerMap['etb:color']);
    },
    mounted() {
      tinymce.init({}).then(() => {
        this.$EventBus.$emit('editor:inited', this.editor);
        this.setContent();
      });

      // 接口外部调用编辑器的命令
      this.$EventBus.$on('editor:cmd', this.EventHandlerMap['editor:cmd']);
      // 修改颜色
      this.$EventBus.$on('etb:color', this.EventHandlerMap['etb:color']);
    },
    methods: {
      /**
       * 处理复制过来的图片
       */
      pasteImageHandler(args) {
        const { dom } = this.editor;
        // 正则
        const pasteReg = /<img.*?src="(.*?)"([^>]+)?>/g;
        // 收集到照片
        let pasteImages = args.content.match(pasteReg);
        let transferData = [];

        if (Array.isArray(pasteImages)) {
          pasteImages.forEach((imageTag) => {
            // 拿到图片地址
            let imageUrl = imageTag.replace(pasteReg, '$1');
            const host = (imageUrl.split('/') || [])[2] || '';
            const isOutterImage = host.indexOf('movtile.com') < 0;

            if (isOutterImage) {
              // 创建ID
              const id = `paste_img_${Math.ceil(Math.random() * 1e4)}`;
              // 替换成自己的tag
              let newImageTag = imageTag.replace(
                '<img',
                `<img class="content-photo-placeholder" id="${id}"`,
              );
              const loadingGIF = 'https://ketang-pub.movtile.com/res/editor-placeholder.png';
              // 把原来的图片地址替换成loading的
              newImageTag = newImageTag.replace(imageUrl, `${loadingGIF}?id=${id}`);

              // 变成loading的动图
              args.content = args.content.replace(imageTag, newImageTag);

              // 整理数据
              if (imageUrl.indexOf('http') > -1) {
                transferData.push({
                  imgId: id,
                  url: imageUrl,
                });
              } else {
                dom.remove(dom.select(`#${id}`));
              }
            }
          });
        }
        if (!Array.isArray(transferData) || transferData.length === 0) return;

        // 统一上传
        transferData.forEach((data) => {
          this.$service
            .SquareUploadTransfer(
              {
                imgs: [data],
              },
              {
                encoding: 'json',
                ...JSON_HEADER,
              },
            )
            .then((res) => {
              if (Array.isArray(res)) {
                // 按照ID 替换
                res.forEach((imageData) => {
                  if (imageData.destUrl) {
                    dom.setAttribs(dom.select(`#${imageData.imgId}`), {
                      src: imageData.destUrl,
                      class: 'content-photo',
                      'data-mce-src': imageData.destUrl,
                    });
                  } else {
                    // 不成功的remove
                    dom.remove(dom.select(`#${imageData.imgId}`));
                  }
                });
              }
            })
            .catch((err) => {
              console.log(err);
              transferData.forEach((imageData) => {
                dom.remove(dom.select(`#${imageData.imgId}`));
              });
              this.$appService.toast({ msg: '图片转存失败,请重试!' });
            })
            .finally(() => {
              // 强制更新 不然会导致数据不同步从而回退的问题
              this.editor.fire('input');
            });
        });
      },
      /**
       * 获取文字内容长度
       */
      getContentLength() {
        const content = this.editor.getContent({ format: 'text' });
        this.contentLength = content.length;
        // console.log(content, content.length);
        this.$emit('content-length', this.contentLength);
      },
      /**
       * 设置光标
       */
      setCursorToEnd() {
        // 在非App下会造成问题
        // if (window.$isApp) {
        this.editor.selection.select(this.editor.getBody(), true);
        this.editor.selection.collapse(false);
        // }
      },
      /**
       * 聚焦到编辑器的最后一行
       */
      focusToEditor() {
        // 其实这个Focus挺好用的,让焦点回到编辑器
        // 而不是去给编辑器文本末尾插一个莫名其妙的东西!!!
        this.editor.focus();
        // this.execCommand('mceInsertContent', false, '');
      },
      /**
       * 设置内容
       */
      setContent() {
        setTimeout(() => {
          if (!this.editor || !this.editor.parser || !this.editor.parser.parse) {
            return this.setContent();
          }

          this.editor.setContent(this.$attrs.value || '', { format: this.contentType });

          if (!this.$attrs.value) {
            this.setPlaceHolder();
          }

          this.editor.on('input execCommand paste redo undo change keyup', (evt) => {
            this.flag = false;
            this.$emit('input', this.editor.getContent({ format: this.contentType }));
            this.getContentLength();
          });
        });
      },
      /**
       * 图片相关
       *
       * 占位和替换占位
       */
      photoTemp(rid) {
        if (!rid) return;
        const fragment = `<p> \
          <img id="${rid}_photo" style="margin: 0 auto" src="${this.placeholderGIF}" class="content-photo-placeholder" /> \
          &nbsp;</p><p>&nbsp;</p>`;

        this.execCommand('mceInsertContent', false, fragment);
        return fragment;
      },
      replacePhoto(payload) {
        if (!payload?.rid) return;
        const { dom } = this.editor;
        const src =
          payload.url +
          (this.workType === 'material'
            ? `?x-oss-process=style/material-content-photo-watermark`
            : '?x-oss-process=image/resize,w_750,mfit');

        dom.setAttribs(dom.select(`#${payload.rid}_photo`), {
          src,
          class: 'content-photo',
          'data-mce-src': src,
        });

        this.$emit('input', this.editor.getContent({ format: this.contentType }));
        // 到最底部
        this.editor.getWin().scrollTo(0, this.editor.getBody().scrollHeight);
      },
      /**
       * 视频相关
       */
      videoTemp(_) {
        if (!_) return;
        // video-box / content-video-cover
        // 这两个class打死都不能变,千万不能变!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        const rid = _.toLowerCase();

        const fragment = `<div contenteditable="false" id="vbox_${rid}" class="video-box"> \
          <img id="cover_${rid}" class="content-video-cover"/> \
          <img id="video_${rid}" class="content-video-placeholder" src="${this.placeholderGIF}" /> \
          &nbsp;</div><p style="text-align:left">&nbsp;</p>`;

        this.execCommand('mceInsertContent', false, fragment);
        return fragment;
      },
      /**
       * 替换视频占位符
       */
      replaceVideo(payload) {
        if (!payload?.vid) return;

        const { dom } = this.editor;
        const rid = payload.rid.toLowerCase();

        dom.setAttribs(dom.select(`#vbox_${rid}`), {
          id: `vbox_${payload.vid}`,
        });

        dom.setAttribs(dom.select(`#cover_${rid}`), {
          id: `cover_${payload.vid}`,
        });

        dom.setAttribs(dom.select(`#video_${rid}`), {
          id: `video_${payload.vid}`,
        });

        this.$emit('input', this.editor.getContent({ format: this.contentType }));
        // 到最底部
        this.editor.getWin().scrollTo(0, this.editor.getBody().scrollHeight);
        this.setCursorToEnd();
      },
      /**
       * 替换视频封面
       */
      setVideoCover(payload) {
        if (!payload?.url) return;

        const { dom } = this.editor;
        const coverElement = dom.select(`#cover_${payload.vid}`);

        dom.setStyles(coverElement, {
          display: 'block',
        });

        dom.setAttribs(coverElement, {
          src: `${payload.url}?x-oss-process=image/resize,w_710,h_376,m_fill`,
        });

        // 移除占位符
        dom.remove(dom.select(`#video_${payload.vid}`));

        this.$emit('input', this.editor.getContent({ format: this.contentType }));
        // 到最底部
        this.editor.getWin().scrollTo(0, this.editor.getBody().scrollHeight);
        this.setCursorToEnd();
      },
      /**
       * 删除上传失败的占位信息
       */
      removeMedia(_) {
        if (!_) return;
        const { dom } = this.editor;
        const rid = _.toLowerCase();
        // 删除
        dom.remove(dom.select(`#vbox_${rid}`));
        dom.remove(dom.select(`#${rid}_photo`));

        this.$emit('input', this.editor.getContent({ format: this.contentType }));
      },
      /**
       * 执行命令
       */
      execCommand(command, ...argus) {
        // this.setCursorToEnd();
        this.editor.execCommand(command, ...argus);
      },
      /**
       * 设置Placeholder
       */
      setPlaceHolder() {
        const isEmpty = this.editor.getContent({ format: 'text' }).length == 0;
        if (isEmpty) {
          const body = this.editor.getBody();
          body.setAttribute(this.placeholderKeyAttr, this.placeholder);
        }
      },
      removePlaceHolder() {
        const body = this.editor.getBody();
        body.removeAttribute(this.placeholderKeyAttr);
      },
    },
  };
</script>

<style lang="less">
  .editor {
    border-bottom: 0.4rem solid #f3f3f3;
    margin-bottom: 2rem;
  }

  .word-limit {
    font-size: 1.4rem;
    color: #666666;
    display: flex;
    justify-content: flex-end;
    margin-bottom: 1rem;

    &.over {
      color: #e23333;
      font-weight: bolder;
    }
  }

  button {
    font-size: 30px;
    padding: 0 10px;
    margin: 10px;
  }

  .tox-tinymce {
    border: none !important;
  }

  .tox .tox-edit-area__iframe {
    background-color: #fff;
    color: #fff;
  }
</style>
